1 module hip.assets.inputmap;
2 import hip.util.reflection;
3 import hip.data.jsonc;
4 import hip.error.handler;
5 import hip.api.input.inputmap;
6 import hip.api.data.asset;
7 import hip.api.input.core;
8 
9 private enum Axes : ubyte
10 {
11     x = 0b1,
12     y = 0b10,
13     z = 0b11,
14     leftStick  = 1 << 2,
15     rightStick = 1 << 3,
16 
17     stickX = 0b01 << 4,
18     stickY = 0b10 << 4,
19     stickZ = 0b11 << 4
20 }
21 
22 Axes getStickFromString(Axes baseAxis, string whichStick, string stickAxis)
23 {
24     Axes stick = whichStick == "left" ? Axes.leftStick : Axes.rightStick;
25     switch(stickAxis)
26     {
27         case "x": return cast(Axes)(Axes.stickX | baseAxis | stick);
28         case "y": return cast(Axes)(Axes.stickY | baseAxis | stick);
29         case "z": return cast(Axes)(Axes.stickZ | baseAxis | stick);
30         default:
31             assert(false, "Can only accept 'x', 'y', 'z' from getStick");
32     }
33 }
34 
35 
36 
37 
38 
39 class HipInputMap : HipAsset, IHipInputMap
40 {
41     alias Context = IHipInputMap.Context;
42     alias AxisContext = IHipInputMap.AxisContext;
43 
44     Context[string] inputMapping;
45 
46     AxisContext[][string] directionalsMapping;
47     ubyte id;
48     this()
49     {
50         super("HipInputMap");
51     }
52     //registerInputAction("menu", MOUSE_BTN_R, TOUCH_0 | TOUCH_1, KEY_WINDOWS)
53     private pragma(inline) Context* getAction(string actionName)
54     {
55         Context* ctx = actionName in inputMapping;
56         if(ctx == null)
57         {
58             ErrorHandler.showErrorMessage("HipInputMap action getter error",
59             '"'~actionName~"' does not exists on input mapping");
60         }
61         return ctx;
62     }
63     private pragma(inline) AxisContext[] getDirectional(string directionalName)
64     {
65         AxisContext[]* ctx = directionalName in directionalsMapping;
66         if(ctx == null)
67         {
68             ErrorHandler.showErrorMessage("HipInputMap directionals getter error",
69             '"'~directionalName~"' does not exists on direction mapping");
70         }
71         return *ctx;
72     }
73 
74     float isActionPressed(string actionName)
75     {
76         Context* c = getAction(actionName);
77         if(!c) return 0.0f;
78         float greatest = 0;
79         foreach(g; c.btns) if(HipInput.isGamepadButtonPressed(g, id))
80             greatest = 1.0f;
81         foreach(k; c.keys) if(HipInput.isKeyPressed(k, id))
82             greatest = 1.0f;
83         return greatest;
84     }
85     float isActionJustPressed(string actionName)
86     {
87         Context* c = getAction(actionName);
88         if(!c) return 0.0f;
89         float greatest = 0;
90         foreach(g; c.btns) if(HipInput.isGamepadButtonJustPressed(g, id))
91             greatest = 1.0f;
92         foreach(k; c.keys) if(HipInput.isKeyJustPressed(k, id))
93             greatest = 1.0f;
94         return greatest;
95     }
96     float isActionJustReleased(string actionName)
97     {
98         Context* c = getAction(actionName);
99         if(!c) return 0.0f;
100         float greatest = 0;
101         foreach(g; c.btns) if(HipInput.isGamepadButtonJustReleased(g, id))
102             greatest = 1.0f;
103         foreach(k; c.keys) if(HipInput.isKeyJustReleased(k, id))
104             greatest = 1.0f;
105         return greatest;
106     }
107 
108 
109     Vector3 getAxis(string directionalName)
110     {
111         AxisContext[] axisArray = getDirectional(directionalName);
112         Vector3 ret;
113         foreach(AxisContext ax; axisArray)
114         {
115             int i = 0;
116             switch(ax.axis & Axes.z)
117             {
118                 case Axes.x: break;
119                 case Axes.y: i = 1; break;
120                 case Axes.z: i = 2; break;
121                 default:
122                     throw new Exception("Invalid Axis context found.");
123             }
124             if(ax.axis & Axes.leftStick || ax.axis & Axes.rightStick)
125             {
126                 ret[i]+= HipInput.getAnalog((ax.axis & Axes.leftStick) != 0 ? HipGamepadAnalogs.leftStick : HipGamepadAnalogs.rightStick, id)[i];
127                 continue;
128             }
129             if(HipInput.isGamepadButtonPressed(ax.btn, id) || HipInput.isKeyPressed(ax.key, id))
130                 ret[i]+= (cast(float)ax.value / 127.0);
131         }
132         return ret;
133     }
134 
135     void registerInputAction(string actionName, Context ctx)
136     {
137         mixin(ErrorHandler.assertReturn!q{actionName != ""}("Register Input Action should contain a name"));
138         inputMapping[actionName] = ctx;
139     }
140     override void onFinishLoading() {}
141     override bool isReady() const { return inputMapping !is null; }
142     override void onDispose() {}
143 
144     static HipInputMap parseInputMap(const ubyte[] file, string fileName, ubyte id = 0)
145     {
146         import hip.util.exception;
147         HipInputMap ret = new HipInputMap();
148         JSONValue inputJson = parseJSON(cast(string)file);
149         enforce(inputJson.type == JSONType.object, "Input map at path "~fileName~" must be an object");
150 
151         //Parse "actions"
152         JSONValue* actions = ("actions" in inputJson);
153         if(actions)
154         {
155             foreach(k, v; actions.object)
156             {
157                 string actionName = k;
158                 JSONValue* kb = ("keyboard" in v.object);
159                 JSONValue* gp = ("gamepad" in v.object);
160 
161                 Context ctx;
162                 if(kb != null)
163                 {
164                     foreach(key; kb.array) //Keyboard
165                         ctx.keys~= key.str[0];
166                 }
167                 if(gp != null)
168                 {
169                     foreach(btn; gp.array) //Gamepad
170                         ctx.btns ~=  gamepadButtonFromString(btn.str);
171                 }
172                 ret.inputMapping[actionName] = ctx;
173                 ctx.name = actionName;
174             }
175         }
176         JSONValue* directionals = "directionals" in inputJson;
177         if(directionals)
178         {
179             enforce(directionals.type == JSONType.object, "Directionals must be an object.");
180             foreach(string directionalName, JSONValue dV; directionals.object)
181             {
182                 enforce(dV.type == JSONType.object, "Directionals must hold an object.");
183                 foreach(string axis, JSONValue content; dV)
184                 {
185                     AxisContext axisCtx;
186                     switch(axis)
187                     {
188                         case "x":
189                             axisCtx.axis = Axes.x;
190                             break;
191                         case "y":
192                             axisCtx.axis = Axes.y;
193                             break;
194                         case "z":
195                             axisCtx.axis = Axes.z;
196                             break;
197                         default:
198                             enforce(false, "Directional named "~directionalName~" must hold an object named 'x', 'y' or 'z'");
199                     }
200                     enforce(content.type == JSONType.array, "Axis '"~axis~"' of directional '"~directionalName~"' must hold an array.");
201                     foreach(JSONValue value; content.array)
202                     {
203                         AxisContext newAxis = axisCtx;
204                         enforce(value.type == JSONType.object, "Axis '"~axis~"' of directional '"~directionalName~"' can only contain an array of objects.");
205 
206                         JSONValue* an = "analog" in value;
207                         JSONValue* kb = "keyboard" in value;
208                         JSONValue* gp = "gamepad" in value;
209                         JSONValue* v = "value" in value;
210 
211                         if(kb || gp)
212                         {
213                             enforce(!an, "If your input has either a keyboard or gamepad, it can't have an analog.");
214                             if(kb)
215                             {
216                                 enforce(!an, "Keyboard can only receive a string.");
217                                 enforce(kb.type == JSONType..string, "Keyboard can only receive a string.");
218                                 newAxis.key = kb.str[0];
219                             }
220                             if(gp)
221                             {
222                                 enforce(gp.type == JSONType..string, "Gamepad can only receive a string.");
223                                 newAxis.btn = gamepadButtonFromString(gp.str);
224                             }
225                             enforce(v && (v.type == JSONType.integer || v.type == JSONType.float_) && (v.floating >= -1.0 && v.floating <= 1.0), "directionals input must have a value and it must be a float between -1 and 1");
226                             newAxis.value = cast(byte)((cast(int)v.floating) * 127);
227                         }
228                         else if(an)
229                         {
230                             enforce(!kb && !gp, "If your input has an analog, it can't contain a gamepad or keyboard");
231                             enforce(an.type == JSONType..string && (an.str == "left" || an.str == "right"), "Your analog must map to either 'left' or 'right'");
232                             JSONValue* stickAxis = "axis" in value;
233                             enforce(stickAxis && stickAxis.type == JSONType..string, "An analog must have an axis, and its type must be a string");
234                             newAxis.axis = getStickFromString(cast(Axes)axisCtx.axis, an.str, stickAxis.str);
235                         }
236 
237                         ret.directionalsMapping[directionalName]~= newAxis;
238                     }
239 
240                 }
241             }
242         }
243 
244         return ret;
245     }
246 }